#ifndef BASETEXTDOCUMENTLAYOUT_H
#define BASETEXTDOCUMENTLAYOUT_H

#include <QPlainTextDocumentLayout>
#include <QTextBlockUserData>

struct Parenthesis;
typedef QVector<Parenthesis> Parentheses;

struct  Parenthesis
{
    enum Type { Opened, Closed };

    inline Parenthesis() : type(Opened), pos(-1) {}
    inline Parenthesis(Type t, QChar c, int position)
        : type(t), chr(c), pos(position) {}
    Type type;
    QChar chr;
    int pos;
};


class CodeFormatterData
{
public:
    virtual ~CodeFormatterData(){}
};


class TextBlockUserData : public QTextBlockUserData
{
public:

    inline TextBlockUserData()
        : m_folded(false),
          m_ifdefedOut(false),
          m_foldingIndent(0),
          m_lexerState(0),
          m_foldingStartIncluded(false),
          m_foldingEndIncluded(false),
          m_codeFormatterData(NULL)
    {}
    ~TextBlockUserData();

    inline int foldingIndent() const { return m_foldingIndent; }
    inline void setFoldingIndent(int indent) { m_foldingIndent = indent; }

    inline void setFoldingStartIncluded(bool included) { m_foldingStartIncluded = included; }
    inline bool foldingStartIncluded() const { return m_foldingStartIncluded; }

    inline void setFoldingEndIncluded(bool included) { m_foldingEndIncluded = included; }
    inline bool foldingEndIncluded() const { return m_foldingEndIncluded; }

    inline void setFolded(bool b) { m_folded = b; }
    inline bool folded() const { return m_folded; }

    inline bool setIfdefedOut() { bool result = m_ifdefedOut; m_ifdefedOut = true; return !result; }
    inline bool clearIfdefedOut() { bool result = m_ifdefedOut; m_ifdefedOut = false; return result;}
    inline bool ifdefedOut() const { return m_ifdefedOut; }

    inline void setParentheses(const Parentheses &parentheses) { m_parentheses = parentheses; }
    inline void clearParentheses() { m_parentheses.clear(); }
    inline const Parentheses &parentheses() const { return m_parentheses; }
    inline bool hasParentheses() const { return !m_parentheses.isEmpty(); }

    static bool findPreviousOpenParenthesis(QTextCursor *cursor, bool select = false,
                                            bool onlyInCurrentBlock = false);
    static bool findNextClosingParenthesis(QTextCursor *cursor, bool select = false);

    static bool findPreviousBlockOpenParenthesis(QTextCursor *cursor, bool checkStartPosition = false);
    static bool findNextBlockClosingParenthesis(QTextCursor *cursor);

    inline int lexerState() const { return m_lexerState; }
    inline void setLexerState(int state) {m_lexerState = state; }

    CodeFormatterData *codeFormatterData() const { return m_codeFormatterData; }
    void setCodeFormatterData(CodeFormatterData *data);


    enum MatchType { NoMatch, Match, Mismatch  };
    static MatchType checkOpenParenthesis(QTextCursor *cursor, QChar c);
    static MatchType checkClosedParenthesis(QTextCursor *cursor, QChar c);
    static MatchType matchCursorBackward(QTextCursor *cursor);
    static MatchType matchCursorForward(QTextCursor *cursor);

private:
    uint m_folded : 1;
    uint m_ifdefedOut : 1;
    uint m_foldingIndent : 16;
    uint m_lexerState : 4;
    uint m_foldingStartIncluded : 1;
    uint m_foldingEndIncluded : 1;
    Parentheses m_parentheses;
    CodeFormatterData *m_codeFormatterData;
};

class BaseTextDocumentLayout : public QPlainTextDocumentLayout
{
    Q_OBJECT
public:
    BaseTextDocumentLayout(QTextDocument *doc);
    ~BaseTextDocumentLayout();

    static void setParentheses(const QTextBlock &block, const Parentheses &parentheses);
    static void clearParentheses(const QTextBlock &block) { setParentheses(block, Parentheses());}
    static Parentheses parentheses(const QTextBlock &block);
    static bool hasParentheses(const QTextBlock &block);

    static void setFoldingIndent(const QTextBlock &block, int indent);
    static int foldingIndent(const QTextBlock &block);
    static bool canFold(const QTextBlock &block);
    static void doFoldOrUnfold(const QTextBlock& block, bool unfold);
    static bool isFolded(const QTextBlock &block);
    static void setFolded(const QTextBlock &block, bool folded);
    static bool setIfdefedOut(const QTextBlock &block);
    static bool clearIfdefedOut(const QTextBlock &block);
    static bool ifdefedOut(const QTextBlock &block);
    static int braceDepth(const QTextBlock &block);
    static void setBraceDepth(QTextBlock &block, int depth);
    static void setLexerState(const QTextBlock &block, int state);
    static int lexerState(const QTextBlock &block);


    static TextBlockUserData *testUserData(const QTextBlock &block) {
        return static_cast<TextBlockUserData*>(block.userData());
    }
    static TextBlockUserData *userData(const QTextBlock &block) {
        TextBlockUserData *data = static_cast<TextBlockUserData*>(block.userData());
        if (!data && block.isValid())
            const_cast<QTextBlock &>(block).setUserData((data = new TextBlockUserData));
        return data;
    }

    class FoldValidator
    {
    public:
        FoldValidator();

        void setup(BaseTextDocumentLayout *layout);
        void reset();
        void process(QTextBlock block);
        void finalize();

    private:
        BaseTextDocumentLayout *m_layout;
        bool m_requestDocUpdate;
        int m_insideFold;
    };

    void updateMarksLineNumber();
    void updateMarksBlock(const QTextBlock &block);

    void setRequiredWidth(int width);
    void emitDocumentSizeChanged() { emit documentSizeChanged(documentSize()); }
protected:
    int m_requiredWidth;
};
#endif // BASETEXTDOCUMENTLAYOUT_H
